#include "CensorSupport.h"

#include <assert.h>
#include <auto_ptr.h>
#include <vector>
#include <iostream>
#include <string>
#include <sstream>

#include "graph_support/impls/vector/VectorGraph.h"

#include "BFSGraphTraversalAlgorithm.h"
#include "EasyRandomGraphGenerator.h"
#include "ChainGraphAlgorithm.h"

using namespace native_orbitor;
using namespace GRAPH_SUPPORT_NS;
using namespace GRAPH_SUPPORT_NS::impls::vector;
using namespace GRAPH_SUPPORT_NS::utils;

namespace orbitor_censor
{

class FakeObject
{
public:
	FakeObject( JNIEnv * env, jobject obj )
		: env( env )
	{
		id = idCount++;
		this->obj = env->NewGlobalRef( obj );

		jclass objClass = env->GetObjectClass( obj );
		assert( objClass );
		jfieldID createdObjectsNotesFieldId = env->GetFieldID( objClass, "createdObjectsNotes", "[I" );
		assert( createdObjectsNotesFieldId );
		jobject createdObjectsNotes = env->GetObjectField( obj, createdObjectsNotesFieldId );
		assert( createdObjectsNotes );
		jintArray createdObjectsNotesArr = ( jintArray ) createdObjectsNotes;
		assert( createdObjectsNotesArr );
		int createdObjectsNotesArrLen = env->GetArrayLength( createdObjectsNotesArr );
		std::vector< int > buf( createdObjectsNotesArrLen );
		env->GetIntArrayRegion( createdObjectsNotesArr, 0, createdObjectsNotesArrLen, & buf[0] );
		int i = 0;
		while ( i < buf.size() && buf[i] >= 0 )
		{
			i++;
		}
		if ( i < buf.size() )
		{
			buf[i] = id;
		}
		env->SetIntArrayRegion( createdObjectsNotesArr, 0, createdObjectsNotesArrLen, & buf[0] );
	}

	~FakeObject()
	{
		jclass objClass = env->GetObjectClass( obj );
		assert( objClass );
		jfieldID createdObjectsNotesFieldId = env->GetFieldID( objClass, "destroyedObjectsNotes", "[I" );
		assert( createdObjectsNotesFieldId );
		jobject createdObjectsNotes = env->GetObjectField( obj, createdObjectsNotesFieldId );
		assert( createdObjectsNotes );
		jintArray createdObjectsNotesArr = ( jintArray ) createdObjectsNotes;
		assert( createdObjectsNotesArr );
		int createdObjectsNotesArrLen = env->GetArrayLength( createdObjectsNotesArr );
		std::vector< int > buf( createdObjectsNotesArrLen );
		env->GetIntArrayRegion( createdObjectsNotesArr, 0, createdObjectsNotesArrLen, & buf[0] );
		int i = 0;
		while ( i < buf.size() && buf[i] >= 0 )
		{
			i++;
		}
		if ( i < buf.size() )
		{
			buf[i] = id;
		}
		env->SetIntArrayRegion( createdObjectsNotesArr, 0, createdObjectsNotesArrLen, & buf[0] );
		env->DeleteGlobalRef( obj );
	}

	int id;
	static int idCount;
	JNIEnv * env;
	jobject obj;
};
int FakeObject::idCount = 1;

void deleteFakeObjectFunc( int foInt )
{
	FakeObject * fo = reinterpret_cast< FakeObject * >( foInt );
	delete fo;
	fo = NULL;
}

}
using namespace orbitor_censor;

JNIEXPORT jintArray JNICALL Java_orbitorcensor_TestObjectsLifetime_generateFakeNativeObject
  ( JNIEnv * env, jobject obj )
{
	std::auto_ptr< FakeObject > foGuard( new FakeObject( env, obj ) );

	int foInt = reinterpret_cast< int >( foGuard.get() );
	void ( * deleteFunc ) ( int ) = & deleteFakeObjectFunc;
	int deleteFuncInt = reinterpret_cast< int >( deleteFunc );

	int buf[] = { foInt, deleteFuncInt };
	jintArray result = env->NewIntArray( 2 );
	env->SetIntArrayRegion( result, 0, 2, buf );

	foGuard.release();

	return result;
}

namespace
{

void check( bool assertion )
{
	if ( ! assertion )
	{
		throw std::exception();
	}
}

}
#define rcheck( a ) assert( ( a ) ) ; check( ( a ) )

std::string gr2str( IGraph & gr )
{
	std::stringstream buf;
	IGraph::IEdgeIteratorPtr eitPtr = gr.igetOutEdgesIterator();
	IGraph::IEdgeIterator & eit = * eitPtr;
	for ( int i = 0; i < gr.igetVertexCount(); i++ )
	{
		for ( gr.igetOutEdgesIterator( i, eit ); eit.notFinished(); eit.next() )
		{
			size_t destVid = eit.getDestination();
			buf << i << " -> " << destVid << ", ";
		}
		buf << ";";
	}
	return buf.str();
}

JNIEXPORT void JNICALL Java_orbitorcensor_TestObjectsLifetime_nativeTestSameGraphGenered
  ( JNIEnv *, jclass )
{
	IGraphGenerator::SettingsMap smap;
	smap["verticesCount"] = 10;
	smap["edgesCount"] = 15;
	smap["randomInt"] = 123;

	EasyRandomGraphGenerator< VectorGraphTmpl< int > > gen0;
	EasyRandomGraphGenerator< VectorGraphTmpl< int > > gen1;

	std::auto_ptr< GraphPointerGuard > grPtr00 = gen0.generateGraph( smap );
	IGraph * gr00 = dynamic_cast< IGraph * >( grPtr00->getAbstractGraphPtr() );
	assert( gr00 );
	std::auto_ptr< GraphPointerGuard > grPtr10 = gen1.generateGraph( smap );
	IGraph * gr10 = dynamic_cast< IGraph * >( grPtr10->getAbstractGraphPtr() );
	assert( gr10 );

	rcheck( std::string(
			"0 -> 6, 0 -> 7, ;1 -> 5, ;;" ) +
			"3 -> 6, ;4 -> 3, 4 -> 8, ;" +
			"5 -> 6, 5 -> 7, 5 -> 3, ;" +
			"6 -> 4, 6 -> 1, ;7 -> 9, 7 -> 5, ;" +
			"8 -> 6, ;9 -> 8, ;" ==
			gr2str( * gr00 ) );
	rcheck( std::string(
			"0 -> 6, 0 -> 7, ;1 -> 5, ;;" ) +
			"3 -> 6, ;4 -> 3, 4 -> 8, ;" +
			"5 -> 6, 5 -> 7, 5 -> 3, ;" +
			"6 -> 4, 6 -> 1, ;7 -> 9, 7 -> 5, ;" +
			"8 -> 6, ;9 -> 8, ;" ==
			gr2str( * gr10 ) );
	rcheck( gr2str( * gr00 ) == gr2str( * gr10 ) );

	smap["randomInt"] = 456;
	std::auto_ptr< GraphPointerGuard > grPtr01 = gen0.generateGraph( smap );
	IGraph * gr01 = dynamic_cast< IGraph * >( grPtr01->getAbstractGraphPtr() );
	assert( gr01 );
	smap["randomInt"] = 789;
	std::auto_ptr< GraphPointerGuard > grPtr11 = gen1.generateGraph( smap );
	IGraph * gr11 = dynamic_cast< IGraph * >( grPtr11->getAbstractGraphPtr() );
	assert( gr11 );
	rcheck( std::string(
			"0 -> 4, 0 -> 9, ;1 -> 5, ;2 -> 1, " ) +
			"2 -> 6, ;3 -> 4, ;4 -> 2, 4 -> 7, 4 -> 8, ;" +
			"5 -> 5, 5 -> 1, ;6 -> 2, 6 -> 3, ;" +
			"7 -> 4, ;8 -> 9, ;;" ==
			gr2str( * gr01 ) );
	rcheck( std::string(
			"0 -> 3, 0 -> 2, 0 -> 3, ;" ) +
			"1 -> 1, 1 -> 5, 1 -> 4, ;" +
			"2 -> 8, ;3 -> 9, 3 -> 5, ;4 -> 7, ;5 -> 8, ;" +
			"6 -> 4, 6 -> 8, ;;" +
			"8 -> 4, ;9 -> 8, ;" ==
			gr2str( * gr11 ) );
	rcheck( gr2str( * gr01 ) != gr2str( * gr11 ) );
}

class FakeGraphAlg : public IGraphAlgorithm
{
public:
	FakeGraphAlg( std::ostream & str )
		: str( str )
	{
	}
	virtual bool isApplicable2graph( Graph * abstractGraphPtr )
	{
		return true;
	}
	virtual void runOnGraph( Graph * abstractGraphPtr )
	{
		str << "FakeGraphAlg on " << ( abstractGraphPtr != NULL );
	}
private:
	std::ostream & str;
};

JNIEXPORT void JNICALL Java_orbitorcensor_TestExperimentRun_native_1testChainAlgorithm
  ( JNIEnv * env, jobject obj )
{
	typedef VectorGraphTmpl< int > GraphI;
	typedef VectorGraphTmpl< long > GraphL;

	EasyRandomGraphGenerator< GraphI > ergg4iO;
	IGraphGenerator & ergg4i = ergg4iO;
	EasyRandomGraphGenerator< GraphL > ergg4lO;
	IGraphGenerator & ergg4l = ergg4lO;
	BFSGraphTraversalAlgorithm< GraphI > alg4iO;
	IGraphAlgorithm & alg4i = alg4iO;
	BFSGraphTraversalAlgorithm< GraphL > alg4lO;
	IGraphAlgorithm & alg4l = alg4lO;

	std::auto_ptr< GraphPointerGuard > graphPtr;

	stdext::hash_map< std::string, double, Hash4StdString > settingsMap;
	settingsMap[std::string( "verticesCount" )] = 10;
	settingsMap[std::string( "edgesCount" )] = 30;
	settingsMap[std::string( "randomInt" )] = 123456789;

	assert( alg4i.isApplicable2graph( ergg4i.generateGraph( settingsMap )->getAbstractGraphPtr() ) );
	alg4i.runOnGraph( ergg4i.generateGraph( settingsMap )->getAbstractGraphPtr() );
	assert( ! alg4l.isApplicable2graph( ergg4i.generateGraph( settingsMap )->getAbstractGraphPtr() ) );

	assert( ! alg4i.isApplicable2graph( ergg4l.generateGraph( settingsMap )->getAbstractGraphPtr() ) );
	assert( alg4l.isApplicable2graph( ergg4l.generateGraph( settingsMap )->getAbstractGraphPtr() ) );
	alg4l.runOnGraph( ergg4l.generateGraph( settingsMap )->getAbstractGraphPtr() );

	{
	ChainGraphAlgorithm chainAlgO;
	IGraphAlgorithm & chainAlg = chainAlgO;
	assert( ! chainAlg.isApplicable2graph( ergg4i.generateGraph( settingsMap )->getAbstractGraphPtr() ) );
	assert( ! chainAlg.isApplicable2graph( ergg4l.generateGraph( settingsMap )->getAbstractGraphPtr() ) );
	}

	{
	ChainGraphAlgorithm chainAlgO;
	chainAlgO.pushbackAllSubalgorithms( ( ChainGraphAlgorithm::AlgPtr[] ) {
			ChainGraphAlgorithm::AlgPtr( new BFSGraphTraversalAlgorithm< GraphL >() ),
			ChainGraphAlgorithm::AlgPtr( new BFSGraphTraversalAlgorithm< GraphI >() ),
		}, 2
	);
	IGraphAlgorithm & chainAlg = chainAlgO;
	assert( chainAlg.isApplicable2graph( ergg4i.generateGraph( settingsMap )->getAbstractGraphPtr() ) );
	assert( chainAlg.isApplicable2graph( ergg4l.generateGraph( settingsMap )->getAbstractGraphPtr() ) );
	chainAlg.runOnGraph( ergg4i.generateGraph( settingsMap )->getAbstractGraphPtr() );
	chainAlg.runOnGraph( ergg4l.generateGraph( settingsMap )->getAbstractGraphPtr() );

	typedef VectorGraphTmpl< const char * > GraphS;
	std::vector< const char * > strs;
	strs.push_back( "a" );
	GraphS grs( strs );
	assert( ! chainAlg.isApplicable2graph( & grs ) );
	std::stringstream sstr;
	ChainGraphAlgorithm::AlgPtr fakeGraphAlg( new FakeGraphAlg( sstr ) );
	chainAlgO.pushbackSubalgorithm( fakeGraphAlg );
	assert( chainAlg.isApplicable2graph( & grs ) );
	chainAlg.runOnGraph( & grs );
	assert( std::string( "FakeGraphAlg on 1" ) == sstr.str() );
	}
}
